package com.opages.mvc.adapter.common.properties;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.MultiValueMap;

import java.util.*;

public class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar,
        ResourceLoaderAware, EnvironmentAware {
    private Environment environment;
    private ResourceLoader resourceLoader;
    // 1、第一步会先执行重写的 registerBeanDefinitions 方法，
    // 入参分别是 AnnotationMetadata 和 BeanDefinitionRegistry。
    // AnnotationMetadata 是获取类的元数据的，如注解信息、 classLoader 等，
    // BeanDefinitionRegistry 则是直接注册所需要的 Bean
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registerInfrastructureBeans(registry);
        // 2、调用 getTypes 方法，返回 Properties 配置类集合。进入 2.1 详细查看
        // 3、调用 register 方法，把 Properties 配置类注册到 Spring 容器中。进入 3.1 详细查看
        getTypes(importingClassMetadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type));
    }

    private List<String> getTypes(AnnotationMetadata metadata) {
        List<String> allConfigurationProperties = new ArrayList<>();
        // 获取指定注解的所有属性值，key是属性名称，Value是值
        Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
        //待扫描的ConfigurationProperties包路径
        final String[] scanPackages = attributes == null ? null : (String[]) attributes.get("basePackages");
        final Class<?>[] determineConfigs = attributes == null ? null : (Class<?>[]) attributes.get("value");
        allConfigurationProperties.addAll(scanConfigurationProperties(scanPackages));
        if(determineConfigs != null || determineConfigs.length > 0) {
            allConfigurationProperties.addAll(toBeanClassName(determineConfigs));
        }
        return allConfigurationProperties;
    }
    private List<String> scanConfigurationProperties(String[] scanPackages) {
        if(scanPackages != null && scanPackages.length > 0) {
            List<String> beanClassNames = new ArrayList<>();
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(ConfigurationProperties.class);
            scanner.addIncludeFilter(annotationTypeFilter);
            for (String pkg : scanPackages) {
                Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(pkg);
                for (BeanDefinition candidateComponent : candidateComponents) {
                    beanClassNames.add(candidateComponent.getBeanClassName());
                }
                return beanClassNames;
            }
        }
        return Collections.EMPTY_LIST;
    }
    private List<String> toBeanClassName(Class<?>[] classes) {
        List<String> beanClassName = new ArrayList<>();
        for(Class<?> clazz : classes) {
            beanClassName.add(clazz.getName());
        }
        return beanClassName;
    }
    private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, String beanClassName) {
        // getName 返回的是 Bean 的名称。进入 3.2 详细查看
//        String name = getName(type);
        // 判断有没有注册过这个 Bean
        if (!beanFactory.containsBean(beanClassName)) {
            // 没有则注册该 Bean。入参是注册器、Bean 的名称、需注册的 Bean。进入 4 详细查看
            registerBeanDefinition(registry, beanClassName);
        }
    }

    private void registerBeanDefinition(BeanDefinitionRegistry registry, String beanClassName) {
        try {
            GenericBeanDefinition definition = new GenericBeanDefinition();
            definition.setBeanClass(Class.forName(beanClassName));
            // 通过 registerBeanDefinition 方法，注册 Bean 。
            // 后期会有 Spring 系列的文章详细介绍该过程，到时候大家再一起讨论。
            registry.registerBeanDefinition(beanClassName, definition);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

    //注册配置文件绑定属性的后置处理器
    static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
        ConfigurationPropertiesBindingPostProcessor.register(registry);
    }
    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}
